using System;
using System.Xml.Serialization;

namespace SharpMud.Text.Matching
{
	/// <summary>
	/// This class encapsulates a string comparison operation in a portable, storable way.
	/// </summary>
	[XmlRoot("StringMatcher")]
	public class StringMatcher
	{
		public const StringLikeCompareMode DefaultStrLikeCompareMethod = StringLikeCompareMode.BinaryCompare;

		#region Private Members
		private string				_Pattern;
		private StringMatchingType	_MatchingType;
		private 
				System.Text.RegularExpressions.Regex 
									_CachedRegEx;
		#endregion

		#region Constructors
		/// <summary>
		/// This is the default constructor for the StringMatcher class
		/// </summary>
		public StringMatcher()
		{
			this._Pattern = String.Empty;
			this._CachedRegEx = null;
		}

		/// <summary>
		/// This is a constructor for the StringMatcher class
		/// </summary>
		/// <param name="matchingType">The type of string comparing to use</param>
		/// <param name="pattern">The pattern to match against</param>
		public StringMatcher(StringMatchingType matchingType, string pattern)
		{
			this._Pattern = pattern;
            this._MatchingType = matchingType;
            if(_MatchingType == StringMatchingType.RegularExpression)
			{
				_CachedRegEx = new System.Text.RegularExpressions.Regex(pattern);
			}
		}	
		#endregion


		/// <summary>
		/// This property gets/sets the type of string comparing to use
		/// </summary>
		[XmlAttribute("MatchMethod")]
		public StringMatchingType MatchingType
		{
			get
			{
				return this._MatchingType;
			}
			set
			{
                if(_MatchingType==value) return;
                if (_MatchingType == StringMatchingType.None)
                    throw new ArgumentNullException("value");
				_MatchingType = value;
				if(!(_MatchingType==StringMatchingType.RegularExpression))
				{
					_CachedRegEx = null;
				}
				else
				{
					_CachedRegEx = new System.Text.RegularExpressions.Regex(this._Pattern);
				}
			}
		}


		/// <summary>
		/// This property gets/sets the pattern to compare against
		/// </summary>
		[XmlText()]
		public string Pattern
		{
			get
			{
				return this._Pattern;
			}
			set
			{
                if((!(value==_Pattern)) && (_MatchingType==StringMatchingType.RegularExpression))
				{
					_CachedRegEx = new System.Text.RegularExpressions.Regex(value);
				}
                _Pattern = value;
			}
		}

		/// <summary>
		/// This method if the entire given string is a match
		/// </summary>
		/// <param name="text">The string to test for a match against</param>
		/// <returns>Returns True if the string is a match, otherwise False.</returns>
		public bool IsMatch(string text)
		{
            if (_MatchingType == StringMatchingType.None)
                throw new InvalidOperationException("String matching cannot be performed when MatchingType has been implicitly or explicitly set to 'None'.");
            
			switch(this._MatchingType)
			{
				case(StringMatchingType.RegEx):
					if(_CachedRegEx.Match(text).Length==text.Length)
						return true;
					else
						return false;
				case(StringMatchingType.Like):
					return Microsoft.VisualBasic.CompilerServices.StringType.StrLike(text,this._Pattern,(Microsoft.VisualBasic.CompareMethod)StringMatcher.DefaultStrLikeCompareMethod);
				case(StringMatchingType.StringLikeBinary):
					return Microsoft.VisualBasic.CompilerServices.StringType.StrLikeBinary(text,this._Pattern);
				case(StringMatchingType.StringLikeTextual):
					return Microsoft.VisualBasic.CompilerServices.StringType.StrLikeText(text,this._Pattern);
				case(StringMatchingType.Exact):
					return (String.Compare(text,this._Pattern,true)==0);
				default:
					throw new InvalidOperationException("StringMatcher has an unrecognized StringMatchingType.");
			}
			return false;
		}

		/// <summary>
		/// This method checks if the given string has any matches
		/// </summary>
		/// <param name="text">The string to test for a match against</param>
		/// <returns>Returns True if the string has a match, otherwise False.</returns>
		public bool HasMatch(string text)
		{
            if(this.Match(text)==null)
                return false;
            else
                return true;
		}

		/// <summary>
		/// This method gets all of the matches that the given string has to this StringMatcher
		/// </summary>
		/// <param name="text">The string to test for a match against</param>
		/// <returns>Returns an array of matches between the text and this StringMatcher</returns>
		public IStringMatcherMatch[] Matches(string text)
		{
            if (_MatchingType == StringMatchingType.None)
                throw new InvalidOperationException("String matching cannot be performed when MatchingType has been implicitly or explicitly set to 'None'.");

			switch(this._MatchingType)
			{
				case(StringMatchingType.RegEx):
					return GetRegexMatches(text);
					break;
				case(StringMatchingType.Like):
					return GetLikeMatches(text);
					break;
				case(StringMatchingType.StringLikeBinary):
					return GetLikeMatches(text,StringLikeCompareMode.BinaryCompare);
					break;
				case(StringMatchingType.StringLikeTextual):
					return GetLikeMatches(text,StringLikeCompareMode.TextCompare);
					break;
				case(StringMatchingType.Exact):
					return GetExactMatches(text);
					break;
				default:
					throw new InvalidOperationException("StringMatcher has an unrecognized StringMatchingType.");
					break;
			}
		}

		/// <summary>
		/// This method gets and replaces all of the matches within a given string with another
		/// string.
		/// </summary>
		/// <param name="text">The text to check for matches in</param>
		/// <param name="replacementString">The text to replace the matches with</param>
		/// <returns>Returns a string with the replacement operation performed</returns>
		public string ReplaceMatches(string text, string replacementString)
		{
            if (_MatchingType == StringMatchingType.None)
                throw new InvalidOperationException("String matching cannot be performed when MatchingType has been implicitly or explicitly set to 'None'.");

            IStringMatcherMatch[] rm = this.Matches(text);
            string s = (string)text.Clone();
            System.Array.Sort(rm);
			foreach(IStringMatcherMatch m in rm)
			{
				s = s.Replace(m.Value, replacementString);
			}
            return s;
		}
		
		/// <summary>
		/// Gets the first match between the given text and this StringMatcher
		/// </summary>
		/// <param name="text">The text to check for a match in</param>
		/// <returns>Returns the first match between the given text and this StringMatcher</returns>
		public SharpMud.Text.Matching.IStringMatcherMatch Match(string text)
		{
            if (_MatchingType == StringMatchingType.None)
                throw new InvalidOperationException("String matching cannot be performed when MatchingType has been implicitly or explicitly set to 'None'.");

			switch(this._MatchingType)
			{
				case(StringMatchingType.RegEx):
					return GetRegexMatch(text);
					break;
				case(StringMatchingType.Like):
					return GetLikeMatch(text);
					break;
				case(StringMatchingType.StringLikeBinary):
					return GetLikeMatch(text,StringLikeCompareMode.BinaryCompare);
					break;
				case(StringMatchingType.StringLikeTextual):
					return GetLikeMatch(text,StringLikeCompareMode.TextCompare);
					break;
				case(StringMatchingType.Exact):
					return GetExactMatch(text);
					break;
				default:
					throw new InvalidOperationException("StringMatcher has an unrecognized StringMatchingType.");
					break;
			}
		}

		private IStringMatcherMatch GetRegexMatch(string text)
		{
            int x;
            System.Text.RegularExpressions.Match m;
            m = this._CachedRegEx.Match(text);
            
            if (m == null)
                return null;
			return (IStringMatcherMatch)(new StringMatcherMatch(m.Index,m.Value));
		}

		private IStringMatcherMatch GetLikeMatch(string text)
		{
			return GetLikeMatch(text,StringMatcher.DefaultStrLikeCompareMethod);
		}

		private IStringMatcherMatch GetLikeMatch(string text, StringLikeCompareMode compareMode)
		{
			int i1;
			int i2;
			for(i1=0;i1<text.Length;i1++)
			{
				for(i2=i1;i2<text.Length;i2++)
				{
					if(Microsoft.VisualBasic.CompilerServices.StringType.StrLike(this.Pattern,text.Substring(i1, i2 - i1),(Microsoft.VisualBasic.CompareMethod)compareMode))
					{
						return (IStringMatcherMatch)(new StringMatcherMatch(i1,text.Substring(i1,i2-i1)));
					}
				}
			}
			return null;
		}

		private IStringMatcherMatch GetExactMatch(string text)
		{
			int i1 = text.IndexOf(this.Pattern,0);
			if(i1>=0)
			{
				return (IStringMatcherMatch)(new StringMatcherMatch(i1,text.Substring(i1,this.Pattern.Length)));
			}
			else
			{
				return null;
			}
		}

		private IStringMatcherMatch[] GetRegexMatches(string text)
		{
			System.Collections.ArrayList al = new System.Collections.ArrayList();
			int x;
			System.Text.RegularExpressions.MatchCollection mc;
			mc = this._CachedRegEx.Matches(text);
			for(x=0;x<mc.Count;x++)
			{
				al.Add((IStringMatcherMatch)(new StringMatcherMatch(mc[x].Index,mc[x].Value)));
			}
			return (IStringMatcherMatch[])al.ToArray(typeof(IStringMatcherMatch));
		}

		private IStringMatcherMatch[] GetLikeMatches(string text)
		{
			return GetLikeMatches(text,StringMatcher.DefaultStrLikeCompareMethod);
		}

		private IStringMatcherMatch[] GetLikeMatches(string text, StringLikeCompareMode compareMode)
		{
			System.Collections.ArrayList al = new System.Collections.ArrayList();
			int i1;
			int i2;
			for(i1=0;i1<text.Length;i1++)
			{
			for(i2=i1;i2<text.Length;i2++)
			{
				if(Microsoft.VisualBasic.CompilerServices.StringType.StrLike(text.Substring(i1, i2 - i1), this.Pattern, (Microsoft.VisualBasic.CompareMethod)compareMode))
				{
					al.Add((IStringMatcherMatch)(new StringMatcherMatch(i1,text.Substring(i1,i2-i1))));
				}
			}
			}
			return (IStringMatcherMatch[]) al.ToArray(typeof(IStringMatcherMatch));
		}

		private IStringMatcherMatch[] GetExactMatches(string text)
		{
			System.Collections.ArrayList al = new System.Collections.ArrayList();
			int i1=0;
			if(text.IndexOf(this.Pattern,0)>=0)
			{
				while(text.IndexOf(this.Pattern,i1)>=0)
				{
					i1 = text.IndexOf(this.Pattern,i1);
					al.Add((IStringMatcherMatch)(new StringMatcherMatch(i1,text.Substring(this.Pattern.Length))));
					i1 = i1 + this.Pattern.Length;
				}
			}
			return (IStringMatcherMatch[]) al.ToArray(typeof(IStringMatcherMatch));
		}

		#region Static Methods
		public static StringMatcher MakeExactMatcher(string pattern)
		{
			return new StringMatcher(StringMatchingType.Exact,pattern);
		}
		public static StringMatcher MakeRegexMatcher(string pattern)
		{
			return new StringMatcher(StringMatchingType.RegEx,pattern);
		}
		public static StringMatcher MakeLikeMatcher(string pattern)
		{
			return new StringMatcher(StringMatchingType.Like,pattern);
		}
		#endregion
	}
}
